Задача тематизации и рекомендации контента Постнауки: https://www.postnauka.ru
Правильная подготовка экспериментов позволяет (или может позволить) добиться:
"Сейчас быстро перегоню тексты в Vowpal Wabbit, и надо пробовать строить модели..."
Если эксперимент пилотный или тестовый - ОК.
Если эксперимент в рамках проекта - так делать НЕЛЬЗЯ.
Невнимательное отношение к данным может привести к печальным последствиям (не только в ТМ, но и вообще в ML). Для правильного построения дальнейшей работы важно:
Форматы данных бывают какие угодно:
Внутри каждого описанного типа данные могут иметь совершенно произвольный вид, то есть написать универсальный парсер данных в Vowpal Wabbit невозможно.
Задача парсинга ложится на пользователя.
Совет: нужно внимательно посмотреть на данные, поискать особенности и ошибки в самих данных!
with [codecs.]open(data_filename ...).re)json, xml, дампы баз данных есть готовые модули для работы, не тратьте время на изобретение велосипедов.less, head, tail.Совет: преобразования данных лучше проводить поэтапно, сохраняя промежуточные результаты
Данные надо поправить так, чтобы:
Если данных много, и из общей схемы парсинга выбиваются доли процента от числа документов -- можно их выбросить и не тратить время.
Пример: коллекция постов Постнауки
import glob
filelist = glob.glob('./data/files/*')
print(u"Число документов: {}".format(len(filelist)))
print filelist[:5]
Число документов: 3446 ['./data/files/21617.txt', './data/files/28563.txt', './data/files/48957.txt', './data/files/9728.txt', './data/files/54464.txt']
import codecs
with codecs.open(filelist[0],'r','utf-8') as f:
lines = []
for line in f:
lines += [line.strip()]
doc = u" ".join(lines)
print doc
Биомедицинская диагностика ------------------------------------------------------------------------------ Химик Евгений Гудилин о диагностике по единичным клеткам и гигантском комбинационном рассеянии ------------------------------------------------------------------------------ Какие существуют методы неинвазивной диагностики? Как возникает явление плазмонного резонанса? И как возможна биомедицинская диагностика без разрушения исследуемых клеток? Об этом рассказывает доктор химических наук Евгений Гудилин.
nltk.tokenizeПример. Русский язык
import re
# для русского языка
text_ru = doc.lower()
text_ru = re.sub(u'([^a-zа-яё]+)',u' \\1 ',text_ru.lower())
text_ru = re.sub(u"\s+",u" ", text_ru).strip()
print text_ru.replace(u' ',u'|')
биомедицинская|диагностика|------------------------------------------------------------------------------|химик|евгений|гудилин|о|диагностике|по|единичным|клеткам|и|гигантском|комбинационном|рассеянии|------------------------------------------------------------------------------|какие|существуют|методы|неинвазивной|диагностики|?|как|возникает|явление|плазмонного|резонанса|?|и|как|возможна|биомедицинская|диагностика|без|разрушения|исследуемых|клеток|?|об|этом|рассказывает|доктор|химических|наук|евгений|гудилин|.
Пример. Английский язык
# для английского языка
text_en = u"Where is your spoon, daddy?"
text_en = re.sub(u'([^a-z]+)',u' \\1 ',text_en.lower())
text_en = re.sub(u"\s+",u" ", text_en).strip()
print text_en.replace(u' ',u'|')
where|is|your|spoon|,|daddy|?
СТЕММИНГ - нормализация слов путем отбрасывания окончаний (согласно правилам, основанным на грамматике языка)
Стеммеры (nltk)
Примеры работы стеммеров:
машины -> машин
дело -> дел
гости -> гость
loving -> love
stemming -> stem
says -> say
why -> whi
Лемматизация - приведение слов к начальной морфологической форме (с помощью словаря и грамматики языка)
Лемматизаторы
Пример. Английский язык
import codecs
import pymorphy2
import nltk
from nltk.corpus import wordnet
from nltk.stem import WordNetLemmatizer
def get_wordnet_pos(treebank_tag):
if treebank_tag.startswith('J'):
return wordnet.ADJ
elif treebank_tag.startswith('V'):
return wordnet.VERB
elif treebank_tag.startswith('N'):
return wordnet.NOUN
elif treebank_tag.startswith('R'):
return wordnet.ADV
else:
return wordnet.NOUN
wnl = WordNetLemmatizer()
doc = text_en.split(u" ")
print [wnl.lemmatize(w,get_wordnet_pos(pos)) for w,pos in nltk.pos_tag(doc)]
[u'where', u'be', u'your', u'spoon', u',', u'daddy', u'?']
Пример. Русский язык
pymorph = pymorphy2.MorphAnalyzer()
temp = []
for word in text_ru.split(u' '):
if not re.match(u'([^a-zа-яё]+)',word):
word = pymorph.parse(word)[0].normal_form
temp += [word]
print u"|".join(temp)
биомедицинский|диагностика|------------------------------------------------------------------------------|химик|евгений|гудилин|о|диагностик|по|единичный|клетка|и|гигантский|комбинационный|рассеяние|------------------------------------------------------------------------------|какой|существовать|метод|неинвазивный|диагностика|?|как|возникать|явление|плазмонный|резонанс|?|и|как|возможный|биомедицинский|диагностика|без|разрушение|исследовать|клетка|?|о|это|рассказывать|доктор|химический|наука|евгений|гудилин|.
Можно выделять:
Как это можно делать:
Ngrammer по мотивам статьи El-Kishky et al, Scalable Topical Phrase Mining from Text Corpora // 2014
Мой код.
Что реализовано:
import os
os.sys.path.append("/home/alex/workspace/tm_nlp/")
from tools import ngrammer
ng = ngrammer.NGrammer()
delimiters = loadFileAsStringArray('stop-words-ru.txt')
ng.delimiters = delimiters # delimiters - это list или dict содержащий слова разделители
delimiters_regex = [u'[^a-zа-яё ]+']
ng.delimiters_regex = delimiters_regex # delimiters_regex - это список regex, описывающий разделители
ng.frequentPhraseMining(corpus_text,threshhold=15,max_ngramm_len=5) # собираем статистику по корпусу текстов corpus_text
ng.saveAsJson('ngrammer_stat.txt',True) # сохраняем состояние энграммера
ng = ngrammer.NGrammer()
ng.loadFromJson('ngrammer_stat.txt',True) # загружаем предварительно сохраненное состояние из файла
res = ng.ngramm(doc, threshhold=5.0) # обрабатываем документ doc. threshhold - порог, который можно покрутить
res = ng.removeDelimiters(res) # можно выкинуть из результата работы слова разделители
Примеры энграмм:
1741 - точка зрение
700 - чёрный дыра
689 - русский язык
563 - речь идти
510 - xx век
476 - крайний мера
464 - xix век
462 - большой количество
440 - друг друг
434 - дать случай
392 - нейтронный звезда
381 - огромный количество
364 - мировой война
297 - элементарный частица
202 - начало xx век
191 - собрать хороший выступление
169 - рассказывать доктор физикий
127 - общий теория относительность
99 - конец xix век
97 - большой адронный коллайдер
89 - физика элементарный частица
78 - вопрос отвечать кандидат
72 - сверхмассивный чёрный дыра
70 - середина xix век
Итак, мы сделали предобработку коллекции: токенизировали, лемматизировали текст, выделили ngramm'ы, сохранили все это в удобном и понятном формате.
Теперь надо посчитать и изучить различные статистики корпуса, подготовить коллекцию в целом для тематического моделирования
Базовые вещи, на которые всегда надо смотреть:
Мой код. Класс Collection позволяет выполнять основные действия с коллекциями
Входные форматы данных:
Загружаем коллекцию
from tools import Collection
# конструктор может быть пустой или принимать пути до файлов vocab или docword UCI формата
collection = Collection.Collection()
collection.verbose = True
collection.load_split_modality_files('pn','./data/pn_all/')
print "Длина словаря:",collection.get_vocabulary().size()
Длина словаря: 95341
Считаем частоты слов
tf_df = collection.tf_df('ngramm')
tf_df = sorted(tf_df.items(),key=lambda x:x[1][0],reverse=True)
voc = collection.vocabulary
for k,v in tf_df[:40]:
print u"{0:<40}{1}".format(voc.get_word(k)[0],v[0])
, 370962 . 179880 в 127488 и 113111 что 51140 это 48539 на 43543 не 42062 с 38524 быть 35561 который 35002 - 32130 — 31974 как 28696 « 28346 то 27953 мы 26547 он 25389 они 24307 тот 23930 этот 23541 о 22151 а 18939 к 18929 но 18171 весь 17854 один 17690 из 17364 по 16957 мочь 16464 : 16359 для 16250 такой 15464 человек 14372 или 14337 есть 14001 ( 13654 год 13592 ? 13397 » 13052
Закон Ципфа, распределение частот слов
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot([tf[1][0] for tf in tf_df[:100]],linewidth=2)
plt.ylabel(u'Frequency')
plt.xlabel(u'Rank')
plt.title(u"Zipf's law")
<matplotlib.text.Text at 0x7fe9ccd51e90>
Закон Хипса, статистика по линам документов
len2len = {}
for doc_id in collection.docword:
bow = collection.get_document(doc_id,None,doc_type='BOW')
len2len.setdefault(sum(bow.values()),[])
len2len[sum(bow.values())] += [len(bow)]
srtd_len = sorted(len2len.items(),key=lambda x: x[0])
doc_len1 = [x[0] for x in srtd_len]
doc_len2 = [sum(x[1])/len(x[1]) for x in srtd_len]
plt.plot(doc_len1,doc_len2,linewidth=2)
plt.ylabel(u'# distinct tokens')
plt.xlabel(u'Length of document')
plt.title(u"Heaps' law")
<matplotlib.text.Text at 0x7fe959a7cd10>
Гистограмма распределения длин документов коллекции
import numpy as np
X = np.arange(len(len2len))
plt.bar(X, [len(x) for x in len2len.values()], align='center', width=0.5)
plt.xticks(np.arange(1, len(len2len.keys()), len(len2len.keys()) / 5))
ymax = max([len(x) for x in len2len.values()]) + 1
plt.ylim(0, ymax)
plt.xlabel(u'Length of document')
plt.ylabel(u'# of documents')
# plt.show()
<matplotlib.text.Text at 0x7fe9849e11d0>
stopwords = {}
with codecs.open('stopwords-ru_pymorphy.txt','r','utf-8') as f:
for line in f:
stopwords[line.strip()] = 1
regex = re.compile(u'[^a-zа-яё ]+')
min_tf = 6
max_tf = 5000
min_length = 3
max_length = 20
to_remove = []
for idx,[tf,df] in tf_df:
word = voc.get_word(idx)[0]
if tf <= min_tf or tf >= max_tf \
or len(word) <= min_length or len(word) > max_length \
or regex.match(word) or stopwords.has_key(word):
to_remove += [idx]
print "Число документов на удаление:", len(to_remove)
Число документов на удаление: 70907
collection.remove_words(to_remove)
print "Длина словаря:",collection.get_vocabulary().size()
Длина словаря: 24434
len2len = {}
min_length = 30
to_remove = []
for doc_id in collection.docword:
doc = collection.get_document(doc_id,None)
if doc.length() < min_length:
to_remove += [doc_id]
print "Число документов на удаление:", len(to_remove)
Число документов на удаление: 279
collection.remove_documents(to_remove)
print "Длина коллекции в документах:",collection.size()
Длина коллекции в документах: 3167
tf_df = collection.tf_df('ngramm')
tf_df = sorted(tf_df.items(),key=lambda x:x[1][0],reverse=True)
plt.plot([tf[1][0] for tf in tf_df[:200]],linewidth=2)
plt.ylabel(u'Frequency')
plt.xlabel(u'Rank')
plt.title(u"Zipf's law")
<matplotlib.text.Text at 0x7fe9619c3c90>
Сохраняем коллекцию в Vowpal Wabbit формате
collection.save_as_vw(filename='data/vw/vw_bow.txt',
vocab_filename='./data/vw/vocab.txt',bow=True)
collection.save_as_vw(filename='data/vw/vw.txt',
vocab_filename='./data/vw/vocab.txt',bow=False)
Какой нужен код
Стоит описать функции единообразного создания, обучения и оценки модели.
def create_model(num_topics, num_processors, params, ...):
...
return model
def train_model(model, num_collection_passes, params, ...):
...
return model
def evaluate_model(model, params, ...):
...
return scores
Стоит описать функцию единообразного сохранения результатов эксперимента.
Совет: сохраняйте абсолютно всю информацию, относящуюся к эксперименту.
def save_results(model_name, model, params, scores, elapsed_times, top_words, out_filename, ...):
Внимательно и понятно описывайте параметры экспериментов
params = {}
params['model_name'] = 'postnauka_plsa_200_topics'
params['num_topics'] = 200
params['num_processors'] = 7
params['num_collection_passes'] = 30
...
Пример кода эксперимента:
params = {}
params['model_name'] = 'postnauka_plsa_200_topics'
params['num_topics'] = 200
params['num_processors'] = 7
params['num_collection_passes'] = 30
...
model = create_model(params,...)
time_start = time.time()
model = train_model(model,params,...)
time_end = time.time()
scores = evaluate_model(model, ...)
save_results(model, params, scores, time_start-time_end, '{}_res.txt'.params['model_name'])
BigARTM - библиотека тематического моделирования, в которой реализованы основные алгоритмы и функции работы с тематическими моделями.
Внутри BigARTM - обобщенный EM-алгоритм для любого типа моделей.

Структура библиотеки:
Ключевые особенности:
Документация: http://docs.bigartm.org/en/stable/
Документация по установке: http://docs.bigartm.org/en/stable/installation (см. свою ОС)
Этапы установки:
Также можно воспользоваться образом docker: https://github.com/bigartm/bigartm-docker
Видеоинструкция по установке (для версии BigARTM 0.7.4):
Документация к python интерфейсу: http://docs.bigartm.org/en/stable/tutorials/python_tutorial.html
Наиболее удобный формат: vowpal wabbit - специальная разметка txt-файла:
Наиболее простой пример:
тематическое моделирование это метод статистический анализ текстовый коллекция
Пример:
Лекция1 |@text тематическое моделирование это метод статистический анализ текстовый коллекция:2 представляться матрица частота слово
BigARTM преобразует данные во внутренний формат - батчи. Батчи сохраняются в заданную директорию и управляются объектом класса BatchVectorizer.
import artm
source_file = os.path.join("data/vw", "vw.txt")
batches_folder = "data/batches"
if not glob.glob(os.path.join(batches_folder, "*")):
batch_vectorizer = artm.BatchVectorizer(data_path=source_file, data_format="vowpal_wabbit",
target_folder=batches_folder, batch_size=400)
else:
batch_vectorizer = artm.BatchVectorizer(data_path=batches_folder,
data_format='batches')
Сбор словаря
dict_name = os.path.join(batches_folder, "dictionary.dict")
dictionary = artm.Dictionary()
if not os.path.exists(dict_name):
dictionary.gather(batches_folder)
dictionary.save(dict_name)
else:
dictionary.load(dict_name)
Объявление метрик качества, снимаемых с моделей
Perplexity (Перплексия)
Перплексия - это мера несоответствия или «удивлённости» модели $p(w|d)$ терминам $w$, наблюдаемым в документах $d$ коллекции $D$, определяемая через логарифм правдоподобия: $$P(D,d)=\exp(-\frac{1}{n}L(\Phi,\Theta))=\exp(-\frac{1}{n}\sum_{d\in D}\sum_{w\in d}n_{dw}\ln{p(w|d)}).$$
scores_list = []
scores_list.append(artm.PerplexityScore(name='PerplexityScore')) # перплексия (перенормированное правдоподобие)
scores_list.append(artm.TopTokensScore(name="top_words",
num_tokens=15,
class_id="ngramm")) # для печати наиболее вероятных терминов темы
Создаем модель
T = 30 # количество тем
model_artm = artm.ARTM(num_topics=T, # число тем
class_ids={"ngramm":1}, # число после названия модальностей - это их веса
num_document_passes=10, # сколько делать проходов по документу
cache_theta=True, # хранить или нет глоабльную матрицу Theta
reuse_theta=False, # если Theta хранится, нужно ли ее вновь инициализировать при каждом проходе
theta_columns_naming="title", # как именовать столбцы в матрице Theta
seed=789, # random seed
scores=scores_list) # метрики качества
Модель нужно инициализировать с помощью словаря. Затем мы сможем ее обучить:
model_artm.initialize(dictionary)
Обучение модели
%time model_artm.fit_offline(batch_vectorizer=batch_vectorizer, num_collection_passes=30)
CPU times: user 1min 27s, sys: 136 ms, total: 1min 27s Wall time: 33.6 s
Изменение перплексии в зависимости от номера прохода по коллекции
plt.plot(model_artm.score_tracker["PerplexityScore"].value[1:])
[<matplotlib.lines.Line2D at 0x7fe9cca36790>]
print "Perplexity:", model_artm.score_tracker["PerplexityScore"].last_value
Perplexity: 3081.28157944
Выведем списки топ-слов
for topic_name in model_artm.topic_names:
print topic_name + ': ',
for word in model_artm.score_tracker["top_words"].last_tokens[topic_name]:
print word,
print
topic_0: частица кварк масса энергия протон симметрия эксперимент электрон нейтрино взаимодействие ускоритель являться стандартный_модель теория состоять topic_1: материал атом структура свет вещество использовать являться электрон система свойство метод устройство энергия элемент состояние topic_2: книга наука автор работа метр история читать написать текст писать читатель тема исследование заниматься литература topic_3: философия текст философ говорить идея культура философский образ мысль платон искусство являться традиция должный смысл topic_4: пространство теория вселенная математика уравнение точка являться физик математический физика эйнштейн квантовый число описывать закон topic_5: сеть интернет дать раса тысяча пользователь неандерталец современный исследователь найти антропология связь друг антрополог исследование topic_6: история музей прошлое память историк театр исторический русь стать образ событие знать представление документ князь topic_7: город царь территория япония государство страна москва земля место центр история большой остров вулкан жить topic_8: университет наука учёный должный образование студент хороший работать работа заниматься большой деньга школа говорить научный topic_9: герой русский искусство мода святилище советский роман праздник авангард писать тело говорить сказка обряд образ topic_10: клетка пациент заболевание болезнь врач препарат лечение медицина ткань являться больной организм система мутация боль topic_11: социология социальный объект социолог пространство отношение теория сообщество являться исследование общество действие социологический наука теоретический topic_12: событие теория движение декарт эффект система гипотеза трудность состояние объяснение образ являться происходить описание модель topic_13: право россия власть закон лекция король прочитать государство император правовой политический публиковать выбирать postnauka должный topic_14: остров сага ирландия церковь тинг король сокотра ведьма знать ирландский образ страна корабль текст история topic_15: животное самец поведение самка группа растение эмоция эволюция птица признак большой насекомое отбор некоторый механизм topic_16: страна экономика экономический китай рынок россия кризис государство компания стать цена большой проблема население развитие topic_17: язык культура народ территория традиция говорить существовать история группа называть восток христианство восточный письменность являться topic_18: язык слово говорить русский_язык словарь лингвист текст русский речь разный значение глагол буква звук знать topic_19: война сталин страна германия власть партия политический советский стать военный немецкий государство мировой_война революция политика topic_20: мозг нейрон память процесс мышление сознание исследование задача связь происходить активность информация область образ проблема topic_21: ребёнок женщина мужчина жизнь pcourse семья отношение общество социальный родитель проблема работа стать группа исследование topic_22: объект видеть глаз предмет восприятие говорить жест знать смотреть образ лицо форма рука информация изображение topic_23: фильм политический государство понятие свобода общество являться культура говорить политика смысл современный кино демократия должный topic_24: земля планета солнце жизнь марс планет космос метеорит атмосфера большой астероид занятие поверхность орбита космический topic_25: клетка молекула белка белок геном бактерия организм вирус структура происходить процесс разный работать образ мембрана topic_26: задача дать система решение модель компьютер программа информация результат алгоритм работать работа компания оценка использовать topic_27: город пространство здание архитектура дискурс образ городской роль являться форма книга жизнь культура работа отношение topic_28: звезда галактика вселенная чёрный_дыра объект вещество большой масса нейтронный_звезда планета наблюдение расстояние солнце излучение размер topic_29: микроорганизм организм робот кислород вода жизнь процесс использовать микроб земля являться условие растение существовать исследование
Для анализа более полного анализа модели хочется:
Основные возможности VisARTM:
Технические подробности:
Полезные ссылки: